#include "model.h"
#include <windows.h>
#include <cassert>
#include <algorithm>
namespace WOW
{

    size_t globalTime = 0;

    void GetBuffer(FILE* f,long offset, void* buf, size_t elementSize, size_t count)
    {
        fseek(f,offset,SEEK_SET);
        fread(buf,elementSize,count,f);
    }

    Vec3D fixCoordSystem(Vec3D v)
    {
        return Vec3D(v.x, v.z, -v.y);
    }

    Vec3D fixCoordSystem2(Vec3D v)
    {
        return Vec3D(v.x, v.z, v.y);
    }

    Quaternion fixCoordSystemQuat(Quaternion v)
    {
        return Quaternion(-v.x, -v.z, v.y, v.w);
    }


    AnimManager::AnimManager(ModelAnimation *anim) {
        AnimIDSecondary = -1;
        SecondaryCount = UPPER_BODY_BONES;
        AnimIDMouth = -1;
        anims = anim;
        AnimParticles = false;

        Count = 1;
        PlayIndex = 0;
        CurLoop = 0;
        animList[0].AnimID = 0;
        animList[0].Loops = 0;

        if (anims != NULL) { 
            Frame = anims[0].timeStart;
            TotalFrames = anims[0].timeEnd - anims[0].timeStart;
        } else {
            Frame = 0;
            TotalFrames = 0;
        }

        Speed = 1.0f;
        mouthSpeed = 1.0f;

        Paused = false;
    }

    AnimManager::~AnimManager() {
        anims = NULL;
    }

    void AnimManager::SetCount(int count)
    {
        Count = count;
    }

    void AnimManager::AddAnim(unsigned int id, short loops) {
        if (Count > 3)
            return;

        animList[Count].AnimID = id;
        animList[Count].Loops = loops;
        Count++;
    }

    void AnimManager::SetAnim(short index, unsigned int id, short loops) {
        // error check, we currently only support 4 animations.
        if (index > 3)
            return;

        animList[index].AnimID = id;
        animList[index].Loops = loops;

        // Just an error check for our "auto animate"
        if (index == 0) {
            Count = 1;
            PlayIndex = index;
            Frame = anims[id].timeStart;
            TotalFrames = anims[id].timeEnd - anims[id].timeStart;
        }

        if (index+1 > Count)
            Count = index+1;
    }

    void AnimManager::Play() {
        PlayIndex = 0;
        //if (Frame == 0 && PlayID == 0) {
        CurLoop = animList[PlayIndex].Loops;
        Frame = anims[animList[PlayIndex].AnimID].timeStart;
        TotalFrames = GetFrameCount();
        //}

        Paused = false;
        AnimParticles = false;
    }

    void AnimManager::Stop() {
        Paused = true;
        PlayIndex = 0;
        Frame = anims[animList[0].AnimID].timeStart;
        CurLoop = animList[0].Loops;
    }

    void AnimManager::Pause(bool force) {
        if (Paused && force == false) {
            Paused = false;
            AnimParticles = !Paused;
        } else {
            Paused = true;
            AnimParticles = !Paused;
        }
    }

    void AnimManager::Next() {
        if(CurLoop == 1) {
            PlayIndex++;
            if (PlayIndex >= Count) {
                Stop();
                return;
            }

            CurLoop = animList[PlayIndex].Loops;
        } else if(CurLoop > 1) {
            CurLoop--;
        } else if(CurLoop == 0) {
            PlayIndex++;
            if (PlayIndex >= Count) {
                PlayIndex = 0;
            }
        }
        // don't change g_selModel->currentAnim in AnimManager
        //g_selModel->currentAnim = animList[PlayIndex].AnimID;

        Frame = anims[animList[PlayIndex].AnimID].timeStart;

        TotalFrames = GetFrameCount();
    }


    void AnimManager::Prev() {
        if(CurLoop >= animList[PlayIndex].Loops) {
            PlayIndex--;

            if (PlayIndex < 0) {
                Stop();
                return;
            }

            CurLoop = animList[PlayIndex].Loops;
        } else if(CurLoop < animList[PlayIndex].Loops) {
            CurLoop++;
        }

        Frame = anims[animList[PlayIndex].AnimID].timeEnd;
        TotalFrames = GetFrameCount();
    }

    int AnimManager::Tick(int time) {
        if((Count < PlayIndex) )
            return -1;

        Frame += int(time*Speed);

        // animate our mouth animation
        if (AnimIDMouth > -1) {
            FrameMouth += (time*mouthSpeed);

            if (FrameMouth >= anims[AnimIDMouth].timeEnd) {
                FrameMouth -= (anims[AnimIDMouth].timeEnd - anims[AnimIDMouth].timeStart);
            } else if (FrameMouth < anims[AnimIDMouth].timeStart) {
                FrameMouth += (anims[AnimIDMouth].timeEnd - anims[AnimIDMouth].timeStart);
            }
        }

        // animate our second (upper body) animation
        if (AnimIDSecondary > -1) {
            FrameSecondary += (time*Speed);

            if (FrameSecondary >= anims[AnimIDSecondary].timeEnd) {
                FrameSecondary -= (anims[AnimIDSecondary].timeEnd - anims[AnimIDSecondary].timeStart);
            } else if (FrameSecondary < anims[AnimIDSecondary].timeStart) {
                FrameSecondary += (anims[AnimIDSecondary].timeEnd - anims[AnimIDSecondary].timeStart);
            }
        }

        if (Frame >= anims[animList[PlayIndex].AnimID].timeEnd) {
            Next();
            return 1;
        } else if (Frame < anims[animList[PlayIndex].AnimID].timeStart) {
            Prev();
            return 1;
        }

        return 0;
    }

    size_t AnimManager::GetFrameCount() {
        return (anims[animList[PlayIndex].AnimID].timeEnd - anims[animList[PlayIndex].AnimID].timeStart);
    }


    void AnimManager::NextFrame()
    {
        //AnimateParticles();
        size_t id = animList[PlayIndex].AnimID;
        Frame += ((anims[id].timeEnd - anims[id].timeStart) / 60);
        TimeDiff = ((anims[id].timeEnd - anims[id].timeStart) / 60);
    }

    void AnimManager::PrevFrame()
    {
        //AnimateParticles();
        size_t id = animList[PlayIndex].AnimID;
        Frame -= ((anims[id].timeEnd - anims[id].timeStart) / 60);
        TimeDiff = ((anims[id].timeEnd - anims[id].timeStart) / 60) * -1;
    }

    void AnimManager::SetFrame(size_t f)
    {
        //TimeDiff = f - Frame;
        Frame = f;
    }

    size_t AnimManager::GetTimeDiff()
    {
        size_t t = TimeDiff;
        TimeDiff = 0;
        return t;
    }

    void AnimManager::SetTimeDiff(size_t i)
    {
        TimeDiff = i;
    }

    void AnimManager::Clear() {
        Stop();
        Paused = true;
        PlayIndex = 0;
        Count = 0;
        CurLoop = 0;
        Frame = 0;
    }

    Model::Model(string name, bool forceAnim) :  forceAnim(forceAnim)
    {
        if (name.empty())
            return;

        modelname=name;
        // Initiate our model variables.
        trans = 1.0f;
        rad = 1.0f;
        pos = Vec3D(0.0f, 0.0f, 0.0f);
        rot = Vec3D(0.0f, 0.0f, 0.0f);

        for (size_t i=0; i<TEXTURE_MAX; i++) {
            specialTextures[i] = -1;
            replaceTextures[i] = 0;
            useReplaceTextures[i] = false;
        }

        for (size_t i=0; i<ATT_MAX; i++) 
            attLookup[i] = -1;

        for (size_t i=0; i<BONE_MAX; i++) 
            keyBoneLookup[i] = -1;


        dlist = 0;
        bounds = 0;
        boundTris = 0;
        showGeosets = 0;

        hasCamera = false;
        hasParticles = false;
        isWMO = false;
        isMount = false;

        showModel = false;
        showBones = false;
        showBounds = false;
        showWireframe = false;
        showParticles = false;
        showTexture = true;

        charModelDetails.Reset();

        vbuf = nbuf = tbuf = 0;

        origVertices = 0;
        vertices = 0;
        normals = 0;
        texCoords = 0;
        indices = 0;

        animtime = 0;
        anim = 0;
        anims = 0;
        animLookups = 0;
        animManager = NULL;
        bones = 0;
        bounds = 0;
        boundTris = 0;
        currentAnim = 0;
        colors = 0;
        globalSequences = 0;
        lights = 0;
        //particleSystems = 0;
        //ribbons = 0;
        texAnims = 0;
        textures = 0;
        transparency = 0;
        events = 0;
        modelType = MT_NORMAL;
        IndiceToVerts = 0;
        // --

        FILE* f=fopen(name.c_str(),"rb");

        ok = false;
        if(!f)
        {
            return;
        }

        if (feof(f)) {
            // delete this; //?
            fclose(f);
            return;
        }
        ok = true;

        fread(&header,sizeof(ModelHeader),1,f);

        //displayHeader(header);

        // Error check
        if (header.id[0] != 'M' && header.id[1] != 'D' && header.id[2] != '2' && header.id[3] != '0') {
            ok = false;
            fclose(f);
            return;
        }

        animated = isAnimated(f) || forceAnim;  // isAnimated will set animGeometry and animTextures

        // Error check
        // 10 1 0 0 = WoW 5.0 models (as of 15464)
        // 10 1 0 0 = WoW 4.0.0.12319 models
        // 9 1 0 0 = WoW 4.0 models
        // 8 1 0 0 = WoW 3.0 models
        // 4 1 0 0 = WoW 2.0 models
        // 0 1 0 0 = WoW 1.0 models
        if (header.version[0] != 4 && header.version[1] != 1 && header.version[2] != 0 && header.version[3] != 0) {
            ok = false;
            fclose(f);

            return;
        }

        if (header.nGlobalSequences) {
            globalSequences = new uint32[header.nGlobalSequences];
            GetBuffer(f,header.ofsGlobalSequences,globalSequences,sizeof(uint32),header.nGlobalSequences);
        }

        if (forceAnim)
            animBones = true;

        if (animated)
            initAnimated(f);
        else
            initStatic(f);

        fclose(f);

        // Ready to render.
        showModel = true;
        if (hasParticles)
            showParticles = true;
        alpha = 1.0f;
    }

    Model::~Model()
    {
        if (ok) {

            // There is a small memory leak somewhere with the textures.
            // Especially if the texture was built into the model.
            // No matter what I try though I can't find the memory to unload.
            if (header.nTextures) {

                // For character models, the texture isn't loaded into the texture manager, manually remove it
                //glDeleteTextures(1, &replaceTextures[1]);

                // Clears textures that were loaded from Model::InitCommon()
                /*for (size_t i=0; i<header.nTextures; i++) {
                if (textures[i]>0)
                texturemanager.del(textures[i]);
                }

                for (size_t i=0; i<TEXTURE_MAX; i++) {
                if (replaceTextures[i] > 0)
                texturemanager.del(replaceTextures[i]);
                }*/

                delete(textures);
            }

            delete(globalSequences);

            delete(bounds);
            delete(boundTris);
            delete(showGeosets);

            delete(animManager);

            if (animated) {
                // unload all sorts of crap
                // Need this if statement because VBO supported
                // cards have already deleted it.

                delete(normals);
                delete(vertices);
                delete(texCoords);

                delete(indices);
                delete(anims);
                delete(animLookups);
                delete(origVertices);
            } else {
                //glDeleteLists(dlist, 1);
            }
        }
    }

    void Model::displayHeader(ModelHeader & a_header)
    {
        std::cout << "id : " << a_header.id[0] << a_header.id[1] << a_header.id[2] << a_header.id[3] << std::endl;
        std::cout << "version : " << (int)a_header.version[0] << (int)a_header.version[1] << (int)a_header.version[2] << (int)a_header.version[3] << std::endl;
        std::cout << "nameLength : " << a_header.nameLength << std::endl;
        std::cout << "nameOfs : " << a_header.nameOfs << std::endl;
        std::cout << "GlobalModelFlags : " << a_header.GlobalModelFlags << std::endl;
        std::cout << "nGlobalSequences : " << a_header.nGlobalSequences << std::endl;
        std::cout << "ofsGlobalSequences : " << a_header.ofsGlobalSequences << std::endl;
        std::cout << "nAnimations : " << a_header.nAnimations << std::endl;
        std::cout << "ofsAnimations : " << a_header.ofsAnimations << std::endl;
        std::cout << "nAnimationLookup : " << a_header.nAnimationLookup << std::endl;
        std::cout << "ofsAnimationLookup : " << a_header.ofsAnimationLookup << std::endl;
        std::cout << "nBones : " << a_header.nBones << std::endl;
        std::cout << "ofsBones : " << a_header.ofsBones << std::endl;
        std::cout << "nKeyBoneLookup : " << a_header.nKeyBoneLookup << std::endl;
        std::cout << "ofsKeyBoneLookup : " << a_header.ofsKeyBoneLookup << std::endl;
        std::cout << "nVertices : " << a_header.nVertices << std::endl;
        std::cout << "ofsVertices : " << a_header.ofsVertices << std::endl;
        std::cout << "nViews : " << a_header.nViews << std::endl;
        std::cout << "nColors : " << a_header.nColors << std::endl;
        std::cout << "ofsColors : " << a_header.ofsColors << std::endl;
        std::cout << "nTextures : " << a_header.nTextures << std::endl;
        std::cout << "ofsTextures : " << a_header.ofsTextures << std::endl;
        std::cout << "nTransparency : " << a_header.nTransparency << std::endl;
        std::cout << "ofsTransparency : " << a_header.ofsTransparency << std::endl;
        std::cout << "nTexAnims : " << a_header.nTexAnims << std::endl;
        std::cout << "ofsTexAnims : " << a_header.ofsTexAnims << std::endl;
        std::cout << "nTexReplace : " << a_header.nTexReplace << std::endl;
        std::cout << "ofsTexReplace : " << a_header.ofsTexReplace << std::endl;
        std::cout << "nTexFlags : " << a_header.nTexFlags << std::endl;
        std::cout << "ofsTexFlags : " << a_header.ofsTexFlags << std::endl;
        std::cout << "nBoneLookup : " << a_header.nBoneLookup << std::endl;
        std::cout << "ofsBoneLookup : " << a_header.ofsBoneLookup << std::endl;
        std::cout << "nTexLookup : " << a_header.nTexLookup << std::endl;
        std::cout << "ofsTexLookup : " << a_header.ofsTexLookup << std::endl;
        std::cout << "nTexUnitLookup : " << a_header.nTexUnitLookup << std::endl;
        std::cout << "ofsTexUnitLookup : " << a_header.ofsTexUnitLookup << std::endl;
        std::cout << "nTransparencyLookup : " << a_header.nTransparencyLookup << std::endl;
        std::cout << "ofsTransparencyLookup : " << a_header.ofsTransparencyLookup << std::endl;
        std::cout << "nTexAnimLookup : " << a_header.nTexAnimLookup << std::endl;
        std::cout << "ofsTexAnimLookup : " << a_header.ofsTexAnimLookup << std::endl;

        //	std::cout << "collisionSphere :" << std::endl;
        //	displaySphere(a_header.collisionSphere);
        //	std::cout << "boundSphere :" << std::endl;
        //	displaySphere(a_header.boundSphere);

        std::cout << "nBoundingTriangles : " << a_header.nBoundingTriangles << std::endl;
        std::cout << "ofsBoundingTriangles : " << a_header.ofsBoundingTriangles << std::endl;
        std::cout << "nBoundingVertices : " << a_header.nBoundingVertices << std::endl;
        std::cout << "ofsBoundingVertices : " << a_header.ofsBoundingVertices << std::endl;
        std::cout << "nBoundingNormals : " << a_header.nBoundingNormals << std::endl;
        std::cout << "ofsBoundingNormals : " << a_header.ofsBoundingNormals << std::endl;

        std::cout << "nAttachments : " << a_header.nAttachments << std::endl;
        std::cout << "ofsAttachments : " << a_header.ofsAttachments << std::endl;
        std::cout << "nAttachLookup : " << a_header.nAttachLookup << std::endl;
        std::cout << "ofsAttachLookup : " << a_header.ofsAttachLookup << std::endl;
        std::cout << "nEvents : " << a_header.nEvents << std::endl;
        std::cout << "ofsEvents : " << a_header.ofsEvents << std::endl;
        std::cout << "nLights : " << a_header.nLights << std::endl;
        std::cout << "ofsLights : " << a_header.ofsLights << std::endl;
        std::cout << "nCameras : " << a_header.nCameras << std::endl;
        std::cout << "ofsCameras : " << a_header.ofsCameras << std::endl;
        std::cout << "nCameraLookup : " << a_header.nCameraLookup << std::endl;
        std::cout << "ofsCameraLookup : " << a_header.ofsCameraLookup << std::endl;
        std::cout << "nRibbonEmitters : " << a_header.nRibbonEmitters << std::endl;
        std::cout << "ofsRibbonEmitters : " << a_header.ofsRibbonEmitters << std::endl;
        std::cout << "nParticleEmitters : " << a_header.nParticleEmitters << std::endl;
        std::cout << "ofsParticleEmitters : " << a_header.ofsParticleEmitters << std::endl;
    }

    bool Model::isAnimated(FILE* f)
    {
        // see if we have any animated bones
        ModelBoneDef *bo = new ModelBoneDef[header.nBones];
        GetBuffer(f,header.ofsBones,bo,sizeof(ModelBoneDef),header.nBones);

        animGeometry = false;
        animBones = false;
        ind = false;

        ModelVertex *verts = new ModelVertex[header.nVertices];
        GetBuffer(f,header.ofsVertices,verts,sizeof(ModelVertex),header.nVertices);
        for (size_t i=0; i<header.nVertices && !animGeometry; i++) {
            for (size_t b=0; b<4; b++) {
                if (verts[i].weights[b]>0) {
                    ModelBoneDef &bb = bo[verts[i].bones[b]];
                    if (bb.translation.type || bb.rotation.type || bb.scaling.type || (bb.flags & MODELBONE_BILLBOARD)) {
                        if (bb.flags & MODELBONE_BILLBOARD) {
                            // if we have billboarding, the model will need per-instance animation
                            ind = true;
                        }
                        animGeometry = true;
                        break;
                    }
                }
            }
        }

        if (animGeometry) 
            animBones = true;
        else {
            for (size_t i=0; i<header.nBones; i++) {
                ModelBoneDef &bb = bo[i];
                if (bb.translation.type || bb.rotation.type || bb.scaling.type) {
                    animBones = true;
                    animGeometry = true;
                    break;
                }
            }
        }

        animTextures = header.nTexAnims > 0;

        bool animMisc = header.nCameras>0 || // why waste time, pretty much all models with cameras need animation
            header.nLights>0 || // same here
            header.nParticleEmitters>0 ||
            header.nRibbonEmitters>0;

        if (animMisc) 
            animBones = true;

        // animated colors
        if (header.nColors) {
            ModelColorDef *cols = new ModelColorDef[header.nColors];
            GetBuffer(f,header.ofsColors,cols,sizeof(ModelColorDef),header.nColors);
            for (size_t i=0; i<header.nColors; i++) {
                if (cols[i].color.type!=0 || cols[i].opacity.type!=0) {
                    animMisc = true;
                    break;
                }
            }
        }

        // animated opacity
        if (header.nTransparency && !animMisc) {
            ModelTransDef *trs = new ModelTransDef[header.nTransparency];
            GetBuffer(f,header.ofsTransparency,trs,sizeof(ModelTransDef),header.nTransparency);
            for (size_t i=0; i<header.nTransparency; i++) {
                if (trs[i].trans.type!=0) {
                    animMisc = true;
                    break;
                }
            }
        }

        // guess not...
        return animGeometry || animTextures || animMisc;
    }


    void Model::initCommon(FILE* f)
    {
        // assume: origVertices already set

        // This data is needed for both VBO and non-VBO cards.
        vertices = new Vec3D[header.nVertices];
        normals = new Vec3D[header.nVertices];

        // Correct the data from the model, so that its using the Y-Up axis mode.
        for (size_t i=0; i<header.nVertices; i++) {
            origVertices[i].pos = fixCoordSystem(origVertices[i].pos);
            origVertices[i].normal = fixCoordSystem(origVertices[i].normal);

            // Set the data for our vertices, normals from the model data
            //if (!animGeometry || !supportVBO) {
            vertices[i] = origVertices[i].pos;
            normals[i] = origVertices[i].normal.normalize();
            //}

            float len = origVertices[i].pos.lengthSquared();
            if (len > rad){ 
                rad = len;
            }
        }

        // model vertex radius
        rad = sqrtf(rad);

        // bounds
        if (header.nBoundingVertices > 0) {
            bounds = new Vec3D[header.nBoundingVertices];
            Vec3D *b = new Vec3D[header.nBoundingVertices];
            GetBuffer(f,header.ofsBoundingVertices,b,sizeof(Vec3D),header.nBoundingVertices);
            for (size_t i=0; i<header.nBoundingVertices; i++) {
                bounds[i] = fixCoordSystem(b[i]);
            }
        }
        if (header.nBoundingTriangles > 0) {
            boundTris = new uint16[header.nBoundingTriangles];
            GetBuffer(f,header.ofsBoundingTriangles,boundTris,sizeof(uint16),header.nBoundingTriangles);
        }

        // textures
        ModelTextureDef *texdef = new ModelTextureDef[header.nTextures];
        GetBuffer(f,header.ofsTextures,texdef,sizeof(ModelTextureDef),header.nTextures);
        if (header.nTextures) {
            textures = new uint32[header.nTextures];
            for (size_t i=0; i<header.nTextures; i++) {

                // Error check
                if (i > TEXTURE_MAX-1) {
                    break;
                }
                if (texdef[i].type == 0) {
                    char* texname=new char[texdef[i].nameLen];
                    GetBuffer(f,texdef[i].nameOfs,texname,sizeof(char),texdef[i].nameLen);
                    TextureList.push_back(texname);
                }
            }
        }
        // replacable textures - it seems to be better to get this info from the texture types
        if (header.nTexReplace) {
            size_t m = header.nTexReplace;
            if (m>16) m = 16;
            int16 *texrep = new int16[header.nTexReplace];
            GetBuffer(f,header.ofsTexReplace,texrep,sizeof(int16),header.nTexReplace);
            for (size_t i=0; i<m; i++) specialTextures[i] = texrep[i];

        }

        // attachments
        if (header.nAttachments) {
            ModelAttachmentDef *attachments = new ModelAttachmentDef[header.nAttachments];
            GetBuffer(f,header.ofsAttachments,attachments,sizeof(ModelAttachmentDef),header.nAttachments);
            for (size_t i=0; i<header.nAttachments; i++) {
                ModelAttachment att;
                att.model = this;
                att.init(f, attachments[i], globalSequences);
                atts.push_back(att);
            }
        }

        if (header.nAttachLookup) {
            int16 *p = new int16[header.nAttachLookup];
            GetBuffer(f,header.ofsAttachLookup,p,sizeof(int16),header.nAttachLookup);
            for (size_t i=0; i<header.nAttachLookup; i++) {
                if (i>ATT_MAX-1)
                    break;
                attLookup[i] = p[i];
            }
        }


        // init colors
        if (header.nColors) {
            colors = new ModelColor[header.nColors];
            ModelColorDef *colorDefs = new ModelColorDef[header.nColors];
            GetBuffer(f,header.ofsColors,colorDefs,sizeof(ModelColorDef),header.nColors);
            for (size_t i=0; i<header.nColors; i++) 
                colors[i].init(f, colorDefs[i], globalSequences);
        }

        // init transparency
        if (header.nTransparency) {
            transparency = new ModelTransparency[header.nTransparency];
            ModelTransDef *trDefs = new ModelTransDef[header.nTransparency];
            GetBuffer(f,header.ofsTransparency,trDefs,sizeof(ModelTransDef),header.nTransparency);
            for (size_t i=0; i<header.nTransparency; i++) 
                transparency[i].init(f, trDefs[i], globalSequences);
        }

        if (header.nViews) {
            // just use the first LOD/view
            // First LOD/View being the worst?
            // TODO: Add support for selecting the LOD.
            // int viewLOD = 0; // sets LOD to worst
            // int viewLOD = header.nViews - 1; // sets LOD to best
            setLOD(f, 0); // Set the default Level of Detail to the best possible. 
        }

        // build indice to vert array.
        if (nIndices) {
            IndiceToVerts = new size_t[nIndices+2];
            for (size_t i=0;i<nIndices;i++){
                size_t a = indices[i];
                for (size_t j=0;j<header.nVertices;j++){
                    if (a < header.nVertices && origVertices[a].pos == origVertices[j].pos){
                        IndiceToVerts[i] = j;
                        break;
                    }
                }
            }
        }
        // zomg done
    }

    void Model::initStatic(FILE* f)
    {
        origVertices = new ModelVertex[header.nVertices];
        GetBuffer(f,header.ofsVertices,origVertices,sizeof(ModelVertex),header.nVertices);

        initCommon(f);

        //dlist = glGenLists(1);
        //glNewList(dlist, GL_COMPILE);

        //glEndList();

        // clean up vertices, indices etc
        delete(vertices);
        delete(normals);
        delete(indices);

        delete(colors);
        delete(transparency);
    }


    void Model::initAnimated(FILE* f)
    {
        if (origVertices) {
            delete [] origVertices;
            origVertices = NULL;
        }

        origVertices = new ModelVertex[header.nVertices];
        GetBuffer(f,header.ofsVertices,origVertices,sizeof(ModelVertex),header.nVertices);
        initCommon(f);

        if (header.nAnimations > 0) {
            anims = new ModelAnimation[header.nAnimations];

            ModelAnimationWotLK animsWotLK;
            char tempname[256];
            for(size_t i=0; i<header.nAnimations; i++) {
                GetBuffer(f,header.ofsAnimations+i*sizeof(ModelAnimationWotLK),&animsWotLK,sizeof(ModelAnimationWotLK),1);

                anims[i].animID = animsWotLK.animID;
                anims[i].timeStart = 0;
                anims[i].timeEnd = animsWotLK.length;
                anims[i].moveSpeed = animsWotLK.moveSpeed;
                anims[i].flags = animsWotLK.flags;
                anims[i].probability = animsWotLK.probability;
                anims[i].d1 = animsWotLK.d1;
                anims[i].d2 = animsWotLK.d2;
                anims[i].playSpeed = animsWotLK.playSpeed;
                anims[i].boundSphere.min = animsWotLK.boundSphere.min;
                anims[i].boundSphere.max = animsWotLK.boundSphere.max;
                anims[i].boundSphere.radius = animsWotLK.boundSphere.radius;
                anims[i].NextAnimation = animsWotLK.NextAnimation;
                anims[i].Index = animsWotLK.Index;

                size_t found=modelname.find_last_of(".");
                sprintf(tempname,"%s%04d-%02d.anim", modelname.substr(0,found).c_str(), anims[i].animID, animsWotLK.subAnimID);
                FILE* tmpFile;
                if (tmpFile = fopen(tempname, "r"))
                {
                    animfiles.push_back(tmpFile);
                }
                else
                    animfiles.push_back(0);
                
            }

            animManager = new AnimManager(anims);
        }

        if (animBones) {
            // init bones...
            bones = new Bone[header.nBones];
            ModelBoneDef *mb = new ModelBoneDef[header.nBones];
            GetBuffer(f,header.ofsBones,mb,sizeof(ModelBoneDef),header.nBones);
            for (size_t i=0; i<header.nBones; i++) {
                bones[i].model = this;
                bones[i].initV3(f, mb[i], globalSequences, animfiles);

            }

            // Block keyBoneLookup is a lookup table for Key Skeletal Bones, hands, arms, legs, etc.
            if (header.nKeyBoneLookup < BONE_MAX) {
                GetBuffer(f,header.ofsKeyBoneLookup,keyBoneLookup,sizeof(int16),header.nKeyBoneLookup);
            } else {
                GetBuffer(f,header.ofsKeyBoneLookup,keyBoneLookup,sizeof(int16),BONE_MAX);
            }
        }

        if (header.nAnimations > 0) {
            for(size_t i=0; i<header.nAnimations; i++) {
                if(animfiles[i])
                    fclose(animfiles[i]);
            }
            animfiles.clear();
        }

        // Index at ofsAnimations which represents the animation in AnimationData.dbc. -1 if none.
        if (header.nAnimationLookup > 0) {
            animLookups = new int16[header.nAnimationLookup];
            GetBuffer(f,header.ofsAnimationLookup,animLookups,sizeof(int16),header.nAnimationLookup);
        }

        const size_t size = (header.nVertices * sizeof(float));
        vbufsize = (3 * size); // we multiple by 3 for the x, y, z positions of the vertex

        texCoords = new Vec2D[header.nVertices];
        for (size_t i=0; i<header.nVertices; i++) 
            texCoords[i] = origVertices[i].texcoords;

        if (animTextures) {
            texAnims = new TextureAnim[header.nTexAnims];
            ModelTexAnimDef *ta = new ModelTexAnimDef[header.nTexAnims];
            GetBuffer(f,header.ofsTexAnims,ta,sizeof(ModelTexAnimDef),header.nTexAnims);
            for (size_t i=0; i<header.nTexAnims; i++)
                texAnims[i].init(f, ta[i], globalSequences);
        }

        if (header.nEvents) {
            ModelEventDef *edefs = new ModelEventDef[header.nEvents];
            GetBuffer(f,header.ofsEvents,edefs,sizeof(ModelEventDef),header.nEvents);
            events = new ModelEvent[header.nEvents];
            for (size_t i=0; i<header.nEvents; i++) {
                events[i].init(f, edefs[i], globalSequences);
            }
        }

        animcalc = false;
    }

    void Model::setLOD(FILE* f, int index)
    {
        // Texture definitions
        ModelTextureDef *texdef = new ModelTextureDef[header.nTextures];
        GetBuffer(f,header.ofsTextures,texdef,sizeof(ModelTextureDef),header.nTextures);

        // Transparency
        int16 *transLookup = new int16[header.nTransparencyLookup];
        GetBuffer(f,header.ofsTransparencyLookup,transLookup,sizeof(int16),header.nTransparencyLookup);

        // I thought the view controlled the Level of detail,  but that doesn't seem to be the case.
        // Seems to only control the render order.  Which makes this function useless and not needed :(
        // remove suffix .M2
        size_t found=modelname.find_last_of(".");
        lodname = modelname.substr(0,found);
        lodname.append("00.skin"); // Lods: 00, 01, 02, 03
        FILE* g=fopen(lodname.c_str(),"rb");
        if (feof(g)) {
            fclose(g);
            return;
        }

        ModelView *view = new ModelView;
        fread(view,sizeof(ModelView),1,g);

        if (view->id[0] != 'S' || view->id[1] != 'K' || view->id[2] != 'I' || view->id[3] != 'N') {
            fclose(g);
            return;
        }

        // Indices,  Triangles
        uint16 *indexLookup = new uint16[view->nIndex];
        GetBuffer(g,view->ofsIndex,indexLookup,sizeof(uint16),view->nIndex);
        uint16 *triangles = new uint16[view->nTris];
        GetBuffer(g,view->ofsTris,triangles,sizeof(uint16),view->nTris);
        nIndices = view->nTris;
        delete(indices);
        indices = new uint16[nIndices];
        for (size_t i = 0; i<nIndices; i++) {
            indices[i] = indexLookup[triangles[i]];
        }

        // render ops
        ModelGeoset *ops = new ModelGeoset[view->nSub];
        GetBuffer(g,view->ofsSub,ops,sizeof(ModelGeoset),view->nSub);

        ModelTexUnit *tex = new ModelTexUnit[view->nTex];
        GetBuffer(g,view->ofsTex,tex,sizeof(ModelTexUnit),view->nTex);



        ModelRenderFlags *renderFlags = new ModelRenderFlags[header.nTexFlags];
        GetBuffer(f,header.ofsTexFlags,renderFlags,sizeof(ModelRenderFlags),header.nTexFlags);
        uint16 *texlookup = new uint16[header.nTexLookup];
        GetBuffer(f,header.ofsTexLookup,texlookup,sizeof(uint16),header.nTexLookup);
        uint16 *texanimlookup = new uint16[header.nTexAnimLookup];
        GetBuffer(f,header.ofsTexAnimLookup,texanimlookup,sizeof(uint16),header.nTexAnimLookup);
        int16 *texunitlookup = new int16[header.nTexUnitLookup];
        GetBuffer(f,header.ofsTexUnitLookup,texunitlookup,sizeof(int16),header.nTexUnitLookup);

        delete(showGeosets);
        showGeosets = new bool[view->nSub];


        for (size_t i=0; i<view->nSub; i++) {
            geosets.push_back(ops[i]);
            showGeosets[i] = true;
        }

        passes.clear();
        for (size_t j = 0; j<view->nTex; j++) {
            ModelRenderPass pass;

            pass.useTex2 = false;
            pass.useEnvMap = false;
            pass.cull = false;
            pass.trans = false;
            pass.unlit = false;
            pass.noZWrite = false;
            pass.billboard = false;
            pass.texanim = -1; // no texture animation

            //pass.texture2 = 0;
            size_t geoset = tex[j].op;

            pass.geoset = (int)geoset;

            pass.indexStart = ops[geoset].istart;
            pass.indexCount = ops[geoset].icount;
            pass.vertexStart = ops[geoset].vstart;
            pass.vertexEnd = pass.vertexStart + ops[geoset].vcount;

            //TextureID texid = textures[texlookup[tex[j].textureid]];
            //pass.texture = texid;
            pass.tex = texlookup[tex[j].textureid];

            // TODO: figure out these flags properly -_-
            ModelRenderFlags &rf = renderFlags[tex[j].flagsIndex];

            pass.blendmode = rf.blend;
            //if (rf.blend == 0) // Test to disable/hide different blend types
            //	continue;

            pass.color = tex[j].colorIndex;
            pass.opacity = transLookup[tex[j].transid];

            pass.unlit = (rf.flags & RENDERFLAGS_UNLIT) != 0;

            pass.cull = (rf.flags & RENDERFLAGS_TWOSIDED) == 0;

            pass.billboard = (rf.flags & RENDERFLAGS_BILLBOARD) != 0;

            // Use environmental reflection effects?
            pass.useEnvMap = (texunitlookup[tex[j].texunit] == -1) && pass.billboard && rf.blend>2; //&& rf.blend<5;

            // Disable environmental mapping if its been unchecked.
            if (pass.useEnvMap)
                pass.useEnvMap = false;

            pass.noZWrite = (rf.flags & RENDERFLAGS_ZBUFFERED) != 0;

            // ToDo: Work out the correct way to get the true/false of transparency
            pass.trans = (pass.blendmode>0) && (pass.opacity>0);	// Transparency - not the correct way to get transparency

            pass.p = ops[geoset].BoundingBox[0].z;

            // Texture flags
            pass.swrap = (texdef[pass.tex].flags & TEXTURE_WRAPX) != 0; // Texture wrap X
            pass.twrap = (texdef[pass.tex].flags & TEXTURE_WRAPY) != 0; // Texture wrap Y

            // tex[j].flags: Usually 16 for static textures, and 0 for animated textures.	
            if (animTextures && (tex[j].flags & TEXTUREUNIT_STATIC) == 0) {
                pass.texanim = texanimlookup[tex[j].texanimid];
            }

            passes.push_back(pass);
        }

        fclose(g);
        // transparent parts come later
        //std::sort(passes.begin(), passes.end());
    }

    void Model::calcBones(size_t anim, size_t time)
    {
        // Reset all bones to 'false' which means they haven't been animated yet.
        for (size_t i=0; i<header.nBones; i++) {
            bones[i].calc = false;
        }

        // Character specific bone animation calculations.
        if (charModelDetails.isChar) {	

            // Animate the "core" rotations and transformations for the rest of the model to adopt into their transformations
            if (keyBoneLookup[BONE_ROOT] > -1)	{
                for (size_t i=0; i<=keyBoneLookup[BONE_ROOT]; i++) {
                    bones[i].calcMatrix(bones, anim, time);
                }
            }

            // Find the close hands animation id
            int closeFistID = 0;
            /*
            for (size_t i=0; i<header.nAnimations; i++) {
            if (anims[i].animID==15) {  // closed fist
            closeFistID = i;
            break;
            }
            }
            */
            // Alfred 2009.07.23 use animLookups to speedup
            if (header.nAnimationLookup >= ANIMATION_HANDSCLOSED && animLookups[ANIMATION_HANDSCLOSED] > 0) // closed fist
                closeFistID = animLookups[ANIMATION_HANDSCLOSED];

            // Animate key skeletal bones except the fingers which we do later.
            // -----
            size_t a, t;

            // if we have a "secondary animation" selected,  animate upper body using that.
            if (animManager->GetSecondaryID() > -1) {
                a = animManager->GetSecondaryID();
                t = animManager->GetSecondaryFrame();
            } else {
                a = anim;
                t = time;
            }

            for (size_t i=0; i<animManager->GetSecondaryCount(); i++) { // only goto 5, otherwise it affects the hip/waist rotation for the lower-body.
                if (keyBoneLookup[i] > -1)
                    bones[keyBoneLookup[i]].calcMatrix(bones, a, t);
            }

            if (animManager->GetMouthID() > -1) {
                // Animate the head and jaw
                if (keyBoneLookup[BONE_HEAD] > -1)
                    bones[keyBoneLookup[BONE_HEAD]].calcMatrix(bones, animManager->GetMouthID(), animManager->GetMouthFrame());
                if (keyBoneLookup[BONE_JAW] > -1)
                    bones[keyBoneLookup[BONE_JAW]].calcMatrix(bones, animManager->GetMouthID(), animManager->GetMouthFrame());
            } else {
                // Animate the head and jaw
                if (keyBoneLookup[BONE_HEAD] > -1)
                    bones[keyBoneLookup[BONE_HEAD]].calcMatrix(bones, a, t);
                if (keyBoneLookup[BONE_JAW] > -1)
                    bones[keyBoneLookup[BONE_JAW]].calcMatrix(bones, a, t);
            }

            // still not sure what 18-26 bone lookups are but I think its more for things like wrist, etc which are not as visually obvious.
            for (size_t i=BONE_BTH; i<BONE_MAX; i++) {
                if (keyBoneLookup[i] > -1)
                    bones[keyBoneLookup[i]].calcMatrix(bones, a, t);
            }
            // =====

            if (charModelDetails.closeRHand) {
                a = closeFistID;
                t = anims[closeFistID].timeStart+1;
            } else {
                a = anim;
                t = time;
            }

            for (size_t i=0; i<5; i++) {
                if (keyBoneLookup[BONE_RFINGER1 + i] > -1) 
                    bones[keyBoneLookup[BONE_RFINGER1 + i]].calcMatrix(bones, a, t);
            }

            if (charModelDetails.closeLHand) {
                a = closeFistID;
                t = anims[closeFistID].timeStart+1;
            } else {
                a = anim;
                t = time;
            }

            for (size_t i=0; i<5; i++) {
                if (keyBoneLookup[BONE_LFINGER1 + i] > -1)
                    bones[keyBoneLookup[BONE_LFINGER1 + i]].calcMatrix(bones, a, t);
            }
        } else {
            for (size_t i=0; i<keyBoneLookup[BONE_ROOT]; i++) {
                bones[i].calcMatrix(bones, anim, time);
            }

            // The following line fixes 'mounts' in that the character doesn't get rotated, but it also screws up the rotation for the entire model :(
            //bones[18].calcMatrix(bones, anim, time, false);

            // Animate key skeletal bones except the fingers which we do later.
            // -----
            size_t a, t;

            // if we have a "secondary animation" selected,  animate upper body using that.
            if (animManager->GetSecondaryID() > -1) {
                a = animManager->GetSecondaryID();
                t = animManager->GetSecondaryFrame();
            } else {
                a = anim;
                t = time;
            }

            for (size_t i=0; i<animManager->GetSecondaryCount(); i++) { // only goto 5, otherwise it affects the hip/waist rotation for the lower-body.
                if (keyBoneLookup[i] > -1)
                    bones[keyBoneLookup[i]].calcMatrix(bones, a, t);
            }

            if (animManager->GetMouthID() > -1) {
                // Animate the head and jaw
                if (keyBoneLookup[BONE_HEAD] > -1)
                    bones[keyBoneLookup[BONE_HEAD]].calcMatrix(bones, animManager->GetMouthID(), animManager->GetMouthFrame());
                if (keyBoneLookup[BONE_JAW] > -1)
                    bones[keyBoneLookup[BONE_JAW]].calcMatrix(bones, animManager->GetMouthID(), animManager->GetMouthFrame());
            } else {
                // Animate the head and jaw
                if (keyBoneLookup[BONE_HEAD] > -1)
                    bones[keyBoneLookup[BONE_HEAD]].calcMatrix(bones, a, t);
                if (keyBoneLookup[BONE_JAW] > -1)
                    bones[keyBoneLookup[BONE_JAW]].calcMatrix(bones, a, t);
            }

            // still not sure what 18-26 bone lookups are but I think its more for things like wrist, etc which are not as visually obvious.
            for (size_t i=BONE_ROOT; i<BONE_MAX; i++) {
                if (keyBoneLookup[i] > -1)
                    bones[keyBoneLookup[i]].calcMatrix(bones, a, t);
            }
        }

        // Animate everything thats left with the 'default' animation
        for (size_t i=0; i<header.nBones; i++) {
            bones[i].calcMatrix(bones, anim, time);
        }
    }

    void Model::animate(size_t anim)
    {
        size_t t=0;

        ModelAnimation &a = anims[anim];
        int tmax = (a.timeEnd-a.timeStart);
        if (tmax==0) 
            tmax = 1;

        if (isWMO == true) {
            t = globalTime;
            t %= tmax;
            t += a.timeStart;
        } else
            t = animManager->GetFrame();

        this->animtime = t;
        this->anim = anim;

        if (animBones) // && (!animManager->IsPaused() || !animManager->IsParticlePaused()))
            calcBones(anim, t);

        if (animGeometry) {

            // transform vertices
            ModelVertex *ov = origVertices;
            for (size_t i=0; i<header.nVertices; ++i,++ov) { //,k=0
                Vec3D v(0,0,0), n(0,0,0);

                for (size_t b=0; b<4; b++) {
                    if (ov->weights[b]>0) {
                        Vec3D tv = bones[ov->bones[b]].mat * ov->pos;
                        Vec3D tn = bones[ov->bones[b]].mrot * ov->normal;
                        v += tv * ((float)ov->weights[b] / 255.0f);
                        n += tn * ((float)ov->weights[b] / 255.0f);
                    }
                }

                vertices[i] = v;
                normals[i] = n;
            }

        }
        if (animTextures) {
            for (size_t i=0; i<header.nTexAnims; i++) {
                texAnims[i].calc(anim, t);
            }
        }
    }





    void ModelEvent::init(FILE* f, ModelEventDef &me, uint32 *globals)
    {
        def = me;
    }


    void TextureAnim::init(FILE* f, ModelTexAnimDef &mta, uint32 *global)
    {
        trans.init(mta.trans, f, global);
        rot.init(mta.rot, f, global);
        scale.init(mta.scale, f, global);
    }


    void Bone::initV3(FILE *f, ModelBoneDef &b, uint32 *global, vector<FILE*> animfiles)
    {
        calc = false;

        parent = b.parent;
        pivot = fixCoordSystem(b.pivot);
        billboard = (b.flags & MODELBONE_BILLBOARD) != 0;
        //billboard = false;

        boneDef = b;

        trans.init(b.translation, f, global, animfiles);
        rot.init(b.rotation, f, global, animfiles);
        scale.init(b.scaling, f, global, animfiles);
        trans.fix(fixCoordSystem);
        rot.fix(fixCoordSystemQuat);
        scale.fix(fixCoordSystem2);
    }



    void Bone::calcMatrix(Bone *allbones, size_t anim, size_t time, bool rotate)
    {
        if (calc)
            return;

        Matrix m;
        Quaternion q;

        bool tr = rot.uses(anim) || scale.uses(anim) || trans.uses(anim) || billboard;
        if (tr) {
            m.translation(pivot);

            if (trans.uses(anim)) {
                Vec3D tr = trans.getValue(anim, time);
                m *= Matrix::newTranslation(tr);
            }

            if (rot.uses(anim) && rotate) {
                q = rot.getValue(anim, time);
                m *= Matrix::newQuatRotate(q);
            }

            if (scale.uses(anim)) {
                Vec3D sc = scale.getValue(anim, time);
                m *= Matrix::newScale(sc);
            }

            if (billboard) {			
                float modelview[16];
                //glGetFloatv(GL_MODELVIEW_MATRIX, modelview);

                Vec3D vRight = Vec3D(modelview[0], modelview[4], modelview[8]);
                Vec3D vUp = Vec3D(modelview[1], modelview[5], modelview[9]); // Spherical billboarding
                //Vec3D vUp = Vec3D(0,1,0); // Cylindrical billboarding
                vRight = vRight * -1;
                m.m[0][2] = vRight.x;
                m.m[1][2] = vRight.y;
                m.m[2][2] = vRight.z;
                m.m[0][1] = vUp.x;
                m.m[1][1] = vUp.y;
                m.m[2][1] = vUp.z;
            }

            m *= Matrix::newTranslation(pivot*-1.0f);

        } else m.unit();

        if (parent > -1) {
            allbones[parent].calcMatrix(allbones, anim, time, rotate);
            mat = allbones[parent].mat * m;
        } else mat = m;

        // transform matrix for normal vectors ... ??
        if (rot.uses(anim) && rotate) {
            if (parent>=0)
                mrot = allbones[parent].mrot * Matrix::newQuatRotate(q);
            else
                mrot = Matrix::newQuatRotate(q);
        } else mrot.unit();

        transPivot = mat * pivot;

        calc = true;
    }


    void TextureAnim::calc(size_t anim, size_t time)
    {
        if (trans.uses(anim)) {
            tval = trans.getValue(anim, time);
        }
        if (rot.uses(anim)) {
            rval = rot.getValue(anim, time);
        }
        if (scale.uses(anim)) {
            sval = scale.getValue(anim, time);
        }
    }


    void ModelTransparency::init(FILE* f, ModelTransDef &mcd, uint32 *global)
    {
        trans.init(mcd.trans, f, global);
    }


    void ModelColor::init(FILE* f, ModelColorDef &mcd, uint32 *global)
    {
        color.init(mcd.color, f, global);
        opacity.init(mcd.opacity, f, global);
    }


    void ModelAttachment::init(FILE* f, ModelAttachmentDef &mad, uint32 *global)
    {
        pos = fixCoordSystem(mad.pos);
        bone = mad.bone;
        id = mad.id;
    }

}